package aceim.protocol.snuk182.icq.inner.dataprocessing; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.RandomAccessFile; import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import aceim.api.utils.Logger; import aceim.protocol.snuk182.icq.inner.ICQConstants; import aceim.protocol.snuk182.icq.inner.ICQServiceInternal; import aceim.protocol.snuk182.icq.inner.ICQServiceResponse; import aceim.protocol.snuk182.icq.inner.dataentity.Flap; import aceim.protocol.snuk182.icq.inner.dataentity.ICBMMessage; import aceim.protocol.snuk182.icq.inner.dataentity.ICQOnlineInfo; import aceim.protocol.snuk182.icq.inner.dataentity.Snac; import aceim.protocol.snuk182.icq.inner.dataentity.TLV; import aceim.protocol.snuk182.icq.utils.ProtocolUtils; import android.os.Environment; /* * it's strongly recommended not even breathe here, unless you're absolutely absolutely sure about what you gonna do... */ public class FileTransferEngine { private static final int SERVER_SOCKET_TIMEOUT = 600000; private static final String proxyUrl = "ars.oscar.aol.com"; private final Map<Long, FileRunnableService> activeTransfers = new HashMap<Long, FileRunnableService>(); private final ICQServiceInternal service; private final List<ICBMMessage> ftMessages = new ArrayList<ICBMMessage>(); private List<NotificationData> notifications = new LinkedList<NotificationData>(); public FileTransferEngine(ICQServiceInternal service) { this.service = service; } public List<ICBMMessage> getMessages() { return ftMessages; } public void fileReceiveResponse(long messageId, Boolean accept) { ICBMMessage message = findMessageByMessageId(messageId); if (message == null) { service.log("ft: no message"); return; } if (!message.senderId.equals(service.getUn())){ message.receiverId = message.senderId; message.senderId = service.getUn(); } message.rvMessageType = (short) (accept ? 2 : 1); if (!accept) { service.log("ft: reject from "+message.receiverId); service.getMessagingEngine().sendFileMessageReject(message); } else { service.log("ft: accept from "+message.receiverId); acceptFile(message); } } private void acceptFile(ICBMMessage message) { connectPeer(message, null, true); } private void createPeer(ICBMMessage message, List<File> files) throws IOException { service.log("ft: creating own peer"); FileRunnableService frs = activeTransfers.get(ProtocolUtils.bytes2LongBE(message.messageId, 0)); if (frs == null){ service.log("ft: new runnable for "+message.receiverId); frs = new FileRunnableService(FileRunnableService.TARGET_PEER, message, files); frs.connectionState = FileRunnableService.CONNSTATE_FILE_HEADER; activeTransfers.put(ProtocolUtils.bytes2LongBE(message.messageId, 0), frs); } else { service.log("ft: existing runnable for "+message.receiverId); frs.message = message; frs.target = FileRunnableService.TARGET_PEER; } frs.connectionState = FileRunnableService.CONNSTATE_FILE_HEADER; frs.server = createLocalSocket(frs); message.externalPort = frs.server.getLocalPort(); message.rvIp = ProtocolUtils.getIPString(service.getInternalIp()); message.rvMessageType = 0; sendFileTransferRequest(message, files); } private void connectPeer(ICBMMessage message, FileRunnableService runnable, boolean incoming) { service.log("connecting peer "+message.rvIp+":"+message.externalPort+"//sender "+message.senderId+"//receiver "+message.receiverId); Socket socket; try { socket = new Socket(); socket.connect(new InetSocketAddress(InetAddress.getByAddress(ProtocolUtils.ipString2ByteBE(message.rvIp)), message.externalPort), 7000); } catch (UnknownHostException e) { service.log(e); socket = null; } catch (IOException e) { service.log(e); socket = null; } if (socket != null && socket.isConnected()) { service.log("ft: direct socket connected for "+message.receiverId); if (runnable == null){ service.log("ft: new runnable for "+message.receiverId); runnable = new FileRunnableService(socket, FileRunnableService.TARGET_PEER, message); runnable.connectionState = FileRunnableService.CONNSTATE_FILE_HEADER; activeTransfers.put(ProtocolUtils.bytes2LongBE(message.messageId, 0), runnable); } else { service.log("ft: existing runnable for "+message.receiverId); if (runnable.server != null){ try { runnable.server.close(); runnable.server = null; } catch (IOException e) { service.log(e); } } runnable.socket = socket; runnable.connectionState = FileRunnableService.CONNSTATE_FILE_HEADER; runnable.target = FileRunnableService.TARGET_PEER; } runnable.start(); if (!message.senderId.equals(service.getUn())){ message.receiverId = message.senderId; } service.getRunnableService().sendToSocket(getAcceptMessage(message)); } else { service.log("ft: no direct connection"); if (incoming/* && checkForClientsDCCapability(message.receiverId)*/){ service.log("ft: creating socket for "+message.receiverId); try { createPeer(message, null); } catch (IOException e) { service.log(e); connectProxy(message, runnable); } } else { connectProxy(message, runnable); } } } @SuppressWarnings("unused") private boolean checkForClientsDCCapability(String receiverId) { String dcCap = ProtocolUtils.getHexString(ICQConstants.CLSID_DIRECT); for (ICQOnlineInfo info: service.getBuddyList().buddyInfos){ if (info.uin.equals(receiverId) && info.capabilities!=null){ for (String cap: info.capabilities){ if (dcCap.equals(cap)){ return true; } } } } return false; } private void connectProxy(ICBMMessage message, FileRunnableService runnable) { Socket socket; try { if (message.connectFTProxy){ service.log("connecting proxy "+message.rvIp+":"+service.getLoginPort()); socket = new Socket(message.rvIp, service.getLoginPort()); } else { service.log("creating proxy call"); socket = new Socket(proxyUrl, service.getLoginPort()); } } catch (UnknownHostException e) { service.log(e); socket = null; } catch (IOException e) { service.log(e); socket = null; } if (socket != null && socket.isConnected()) { if (runnable == null) { runnable = new FileRunnableService(socket, FileRunnableService.TARGET_PROXY, message); activeTransfers.put(ProtocolUtils.bytes2LongBE(message.messageId, 0), runnable); } else { if (runnable.server != null){ try { runnable.server.close(); runnable.server = null; } catch (IOException e) { service.log(e); } } runnable.socket = socket; runnable.connectionState = FileRunnableService.CONNSTATE_HANDSHAKE; runnable.target = FileRunnableService.TARGET_PROXY; runnable.message = message; } runnable.start(); } else { transferFailed(new IOException("Cannot connect"), "", message, message.senderId); } } public ICBMMessage findMessageByMessageId(long messageId) { for (ICBMMessage msg : ftMessages) { if (ProtocolUtils.bytes2LongBE(msg.messageId, 0) == messageId) { return msg; } } return null; } public ICBMMessage findMessageByMessageId(byte[] messageId) { for (ICBMMessage msg : ftMessages) { if (Arrays.equals(messageId, msg.messageId)) { return msg; } } return null; } public void removeMessageByMessageId(long messageId) { for (int i=ftMessages.size()-1; i>=0; i--) { ICBMMessage msg = ftMessages.get(i); if (ProtocolUtils.bytes2LongBE(msg.messageId, 0) == messageId) { ftMessages.remove(i); break; } } } class FileRunnableService extends Thread { public static final int CONNSTATE_CONNECTED = 0; public static final int CONNSTATE_HANDSHAKE = 1; public static final int CONNSTATE_FILE_HEADER = 2; public static final int CONNSTATE_FILE_BODY = 3; public static final int CONNSTATE_FILE_SENT = 4; public static final int CONNSTATE_DISCONNECTED = 5; public static final int TARGET_PEER = 0; public static final int TARGET_PROXY = 1; ServerSocket server = null; Socket socket; int connectionState = CONNSTATE_CONNECTED; int target; ICBMMessage message; String participantUid = null; List<byte[]> blobs = new LinkedList<byte[]>(); List<File> files = null; long currentFileSizeLeft = 0; long currentFileSize = 0; byte[] currentFileInfo = null; int totalFiles = 1; private ExtendedBufferedOutputStream currentFileStream; private String currentFileName; byte[] buffer = null; public FileRunnableService(Socket socket, int target, ICBMMessage message) { this(socket, target, message, null); } public FileRunnableService(int target, ICBMMessage message, List<File> files) { this(null, target, message, files); } public FileRunnableService(Socket socket, int target, ICBMMessage message, List<File> files) { this.socket = socket; this.target = target; this.message = message; this.files = files; if (files != null) { totalFiles = files.size(); currentFileSize = 0; for (File f : files) { currentFileSize += f.length(); } } if (message.senderId !=null && !message.senderId.equals(service.getUn())){ participantUid = message.senderId; } else { participantUid = message.receiverId; } setName("File transfer " + message.senderId); } @Override public void run() { if (socket == null) { return; } if (message.receiverId.equals(service.getUn())){ message.receiverId = message.senderId; message.senderId = service.getUn(); } if (target == TARGET_PROXY) { sendHandshake(); } else if (files != null && files.size()>0 && message.connectFTPeer) { connectionState = CONNSTATE_FILE_HEADER; fireTransfer(); } /*else { if (files != null) { sendFileInfo(files.get(0)); } }*/ getDataFromSocket(); } private void sendFileInfo(File file) { byte[] infoBlob; byte[] filenameBytes = file.getName().getBytes(); String encoding = null;/*"UTF-16BE"; try { filenameBytes = file.getName().getBytes(encoding); } catch (UnsupportedEncodingException e) { filenameBytes = file.getName().getBytes(); encoding = "UTF-8"; }*/ if (filenameBytes.length + 194 > 256){ infoBlob = new byte[filenameBytes.length + 194]; } else { infoBlob = new byte[256]; } Arrays.fill(infoBlob, (byte) 0); infoBlob[0] = 0x4f; // file header infoBlob[1] = 0x46; infoBlob[2] = 0x54; infoBlob[3] = 0x32; int pos = 4; System.arraycopy(ProtocolUtils.short2ByteBE((short) infoBlob.length), 0, infoBlob, pos, 2); pos+=2; System.arraycopy(ProtocolUtils.short2ByteBE((short) 0x101), 0, infoBlob, pos, 2); //stage pos+=2; System.arraycopy(message.messageId, 0, infoBlob, pos, 8); //cookie; pos+=8; System.arraycopy(new byte[]{0,0}, 0, infoBlob, pos, 2); //encryption pos+=2; System.arraycopy(new byte[]{0,0}, 0, infoBlob, pos, 2); //compression pos+=2; System.arraycopy(ProtocolUtils.short2ByteBE((short) totalFiles), 0, infoBlob, pos, 2); //total files pos+=2; System.arraycopy(ProtocolUtils.short2ByteBE((short) files.size()), 0, infoBlob, pos, 2); // files left pos+=2; System.arraycopy(new byte[]{0,1}, 0, infoBlob, pos, 2); //dunno pos+=2; System.arraycopy(new byte[]{0,1}, 0, infoBlob, pos, 2); //dunno pos+=2; System.arraycopy(ProtocolUtils.int2ByteBE((int) currentFileSize), 0, infoBlob, pos, 4); pos+=4; System.arraycopy(ProtocolUtils.int2ByteBE((int) file.length()), 0, infoBlob, pos, 4); pos+=4; System.arraycopy(ProtocolUtils.int2ByteBE((int) file.lastModified()/1000), 0, infoBlob, pos, 4); pos+=4; try { System.arraycopy(ProtocolUtils.int2ByteBE((int) getChecksum(file)), 0, infoBlob, pos, 4); } catch (IOException e) { service.log(e); System.arraycopy(ProtocolUtils.int2ByteBE( 0), 0, infoBlob, pos, 4); } pos+=4; System.arraycopy(new byte[]{(byte) 0xff,(byte) 0xff,0,0}, 0, infoBlob, pos, 4); //dunno what's that pos+=4; System.arraycopy(new byte[]{0,0,0,0}, 0, infoBlob, pos, 4); pos+=4; System.arraycopy(new byte[]{0,0,0,0}, 0, infoBlob, pos, 4); pos+=4; System.arraycopy(new byte[]{(byte) 0xff,(byte) 0xff,0,0}, 0, infoBlob, pos, 4); pos+=4; System.arraycopy(new byte[]{0,0,0,0}, 0, infoBlob, pos, 4); pos+=4; System.arraycopy(new byte[]{(byte) 0xff,(byte) 0xff,0,0}, 0, infoBlob, pos, 4); pos+=4; byte[] id = new String("Cool FileXfer").getBytes(); System.arraycopy(id, 0, infoBlob, pos, id.length); pos+=32; infoBlob[pos] = 0x20; pos++; infoBlob[pos] = 0x1c; pos++; infoBlob[pos] = 0x11; pos++; pos+=69; //dummy? pos+=16; //mac file info? //if (encoding.equals("UTF-16BE")) { // System.arraycopy(new byte[]{0,2}, 0, infoBlob, pos, 2); //encoding //} else { System.arraycopy(new byte[]{0,0}, 0, infoBlob, pos, 2); //encoding //} pos+=2; System.arraycopy(new byte[]{0,0}, 0, infoBlob, pos, 2); //encoding subcode pos+=2; System.arraycopy(filenameBytes, 0, infoBlob, pos, filenameBytes.length); sendToSocket(infoBlob); } private void sendHandshake() { sendToSocket(getHandshakeData(message)); } private boolean getDataFromSocket() { byte[] tail = null; int read = 0; int tailLength = 0; while (connectionState != CONNSTATE_DISCONNECTED && socket != null && socket.isConnected() && !socket.isClosed()) { InputStream is; try { is = socket.getInputStream(); if (is.available() < 1) { Thread.sleep(300); } else { Thread.sleep(500); if (tail == null) { byte[] lengthBytes; switch (connectionState) { case CONNSTATE_CONNECTED: case CONNSTATE_HANDSHAKE: lengthBytes = new byte[2]; is.read(lengthBytes, 0, 2); tailLength = ProtocolUtils.unsignedShort2Int(ProtocolUtils.bytes2ShortBE(lengthBytes)); break; case CONNSTATE_FILE_HEADER: byte[] fileHdrMark = new byte[4]; is.read(fileHdrMark, 0, 4); if (fileHdrMark[0] == 0x4f // file header && fileHdrMark[1] == 0x46 && fileHdrMark[2] == 0x54 && fileHdrMark[3] == 0x32) { lengthBytes = new byte[2]; is.read(lengthBytes, 0, 2); tailLength = ProtocolUtils.unsignedShort2Int(ProtocolUtils.bytes2ShortBE(lengthBytes)) - 6; } break; case CONNSTATE_FILE_BODY: if (buffer == null) { buffer = new byte[88000]; } read = is.read(buffer, 0, buffer.length); service.log("read " + read+"| bytes left " + currentFileSizeLeft); currentFileSizeLeft -= read; fileData(buffer, read, currentFileSizeLeft); if (currentFileSizeLeft < 1) { sendFileAck(); connectionState = CONNSTATE_FILE_HEADER; totalFiles--; buffer = null; if (totalFiles < 1) { cleanup(); } } /* * if (buffer.length >= currentFileSizeLeft){ * //read = is.read(buffer, 0, buffer.length); * //buffer = new byte[(int) * currentFileSizeLeft]; read = is.read(buffer, * 0, buffer.length); service.log("read "+read); * * currentFileSizeLeft-=read; fileData(buffer, * read, currentFileSizeLeft); * * if (currentFileSizeLeft < 1){ sendFileAck(); * connectionState = CONNSTATE_FILE_HEADER; * totalFiles--; buffer = null; if (totalFiles < * 1){ ftMessages.remove(message); * activeTransfers.remove(message.messageId); * socket.close(); } } * * } else { read = is.read(buffer, 0, * buffer.length); service.log("read "+read); * currentFileSizeLeft-=read; fileData(buffer, * read, currentFileSizeLeft); } */ continue; } read = 0; tail = new byte[tailLength]; read += is.read(tail, 0, tailLength); service.log("-- FT Got " + ProtocolUtils.getSpacedHexString(tail)); if (read < tailLength) { continue; } } else { read += is.read(tail, 6 + read, tailLength - read); if (read < tailLength) { continue; } } try { blobs.add(tail); } catch (Exception e) { service.log(e); } new Thread("File transfer processor") { @Override public void run() { try { forceBlobProcess(); } catch (Exception e) { service.log(e); } } }.start(); tail = null; } } catch (IOException e) { service.log(e); } catch (InterruptedException e) { service.log(e); } } cleanup(); return false; } private void sendFileAck() { if (currentFileInfo == null) { return; } byte[] out = getFileInfoByteBlock(currentFileInfo, (short) 0x204); sendToSocket(out); } public synchronized boolean sendToSocket(byte[] out) { try { OutputStream os = socket.getOutputStream(); service.log("-- FT To be sent " + ProtocolUtils.getSpacedHexString(out)); os.write(out); } catch (IOException e) { connectionState = CONNSTATE_DISCONNECTED; service.log(e); } return true; } protected void forceBlobProcess() throws Exception { synchronized (blobs) { while (blobs.size() > 0) { byte[] blob = blobs.remove(0); process(blob); } } } private void process(byte[] blob) { switch (connectionState) { case CONNSTATE_CONNECTED: case CONNSTATE_HANDSHAKE: parseRendezvous(blob); break; case CONNSTATE_FILE_HEADER: service.log("got header"); currentFileInfo = blob; parseFileInfoBlob(blob); break; case CONNSTATE_FILE_BODY: // fileData(blob); break; } } public void parseRendezvous(byte[] blob) { switch (blob[3]) { case 0x3: connectionState = CONNSTATE_HANDSHAKE; int portId = ProtocolUtils.unsignedShort2Int(ProtocolUtils.bytes2ShortBE(blob, 10)); String rvIp = ProtocolUtils.getIPString(blob, 12); message.rvMessageType = 0; message.receiverId = participantUid; //if (files == null){ service.getRunnableService().sendToSocket(getRedirectToProxyMessage(rvIp, portId, message, 2)); /*} else { service.getRunnableService().sendToSocket(getAcceptMessage(message)); }*/ break; case 0x4: //sendFileInfo(files.get(0)); break; case 0x5: connectionState = CONNSTATE_FILE_HEADER; if (files != null){ message.receiverId = participantUid; service.getRunnableService().sendToSocket(getAcceptMessage(message)); fireTransfer(); } break; } } private void parseFileInfoBlob(byte[] blob) { // assume we have no 6 bytes of header in a blob int pos = 0; short stage = ProtocolUtils.bytes2ShortBE(blob, pos); pos += 2; pos += 8; // skip msg cookie //short encryption = ProtocolUtils.bytes2ShortBE(blob, pos); pos += 2; //short compression = ProtocolUtils.bytes2ShortBE(blob, pos); pos += 2; int filesCount = ProtocolUtils.unsignedShort2Int(ProtocolUtils.bytes2ShortBE(blob, pos)); pos += 2; //int filesLeft = ProtocolUtils.unsignedShort2Int(ProtocolUtils.bytes2ShortBE(blob, pos)); pos += 2; //int partsCount = ProtocolUtils.unsignedShort2Int(ProtocolUtils.bytes2ShortBE(blob, pos)); pos += 2; pos += 2; // dunno what's there //long totalFilesize = ProtocolUtils.unsignedInt2Long(ProtocolUtils.bytes2IntBE(blob, pos)); pos += 4; long thisFileSize = ProtocolUtils.unsignedInt2Long(ProtocolUtils.bytes2IntBE(blob, pos)); pos += 4; long modTime = ProtocolUtils.unsignedInt2Long(ProtocolUtils.bytes2IntBE(blob, pos))*1000; pos += 4; //long checksum = ProtocolUtils.unsignedInt2Long(ProtocolUtils.bytes2IntBE(blob, pos)); pos += 4; pos += 16; // dunno what's there ^^ long thisFileSizeSent = ProtocolUtils.unsignedInt2Long(ProtocolUtils.bytes2IntBE(blob, pos)); pos += 4; @SuppressWarnings("unused") long checksumSent = ProtocolUtils.unsignedInt2Long(ProtocolUtils.bytes2IntBE(blob, pos)); pos += 4; pos += 32; // skip ID //byte flags = blob[pos]; pos++; pos += 2; // skip some offsets pos += 69; // skip dummy pos += 16; // skip mac file info short charsetType = ProtocolUtils.bytes2ShortBE(blob, pos); pos += 2; short charsetSubcode = ProtocolUtils.bytes2ShortBE(blob, pos); pos += 2; service.log("char code "+charsetType+"|char subcode "+charsetSubcode); int filenamePos = pos; for (int i=blob.length-2; i>pos; i--){ if (blob[i] != 0){ pos = i+1; break; } } int filemaneSize = pos - filenamePos; byte[] filenameBytes = new byte[filemaneSize]; System.arraycopy(blob, filenamePos, filenameBytes, 0, filemaneSize); String encoding; switch (charsetType) { case 0: encoding = "UTF-8"; break; case 2: encoding = "UTF-16"; break; default: encoding = "windows-1251"; break; } String filename; try { filename = new String(filenameBytes, encoding); } catch (UnsupportedEncodingException e) { filename = new String(filenameBytes); } if (files == null){ totalFiles = filesCount; service.log("file info " + filename + " sized " + thisFileSize); currentFileSizeLeft = thisFileSize; currentFileSize = thisFileSize; currentFileStream = createFile(filename, thisFileSize, modTime, message, participantUid); currentFileName = filename; byte[] out = getFileInfoByteBlock(blob, (short) 0x202); sendToSocket(out); connectionState = CONNSTATE_FILE_BODY; } else { switch(stage){ case 0x202: connectionState = CONNSTATE_FILE_BODY; sendFileToSocket(files.get(0)); break; case 0x204: files.remove(0); if (files.size()<1){ cleanup(); } else { sendFileInfo(files.get(0)); } break; case 0x205: byte[] out2 = getFileInfoByteBlock(blob, (short) 0x106); sendToSocket(out2); break; case 0x106: break; case 0x207: connectionState = CONNSTATE_FILE_BODY; sendFileToSocket(files.get(0), thisFileSizeSent); break; } } } private void sendFileToSocket(final File file){ sendFileToSocket(file, 0); } private void sendFileToSocket(final File file, long startFrom) { OutputStream os; try { os = socket.getOutputStream(); } catch (IOException e) { service.log(e); transferFailed(e, file.getAbsolutePath(), message, participantUid); cleanup(); return; } long length = file.length(); if (length > 8000){ buffer = new byte[8000]; } else { buffer = new byte[(int) length]; } currentFileSizeLeft = 0; int read = 0; service.log("sending "+file.getName()+" to "+participantUid); BufferedInputStream bis = null; try { FileInputStream fis = new FileInputStream(file); if (startFrom > 0){ fis.skip(startFrom); currentFileSizeLeft += startFrom; } bis = new BufferedInputStream(fis, 8000); while(currentFileSizeLeft < length){ read = bis.read(buffer, 0, buffer.length); if (read < 0){ break; } os.write(buffer, 0, read); os.flush(); currentFileSizeLeft += read; service.log("sent "+currentFileSizeLeft+" bytes"); sendNotification(message.messageId, file.getAbsolutePath(), length, currentFileSizeLeft, false, null, participantUid); try { Thread.sleep(500); } catch (InterruptedException e) { service.log(e); } } } catch (IOException e) { service.log(e); transferFailed(e, file.getAbsolutePath(), message, participantUid); cleanup(); return; } finally { if (bis != null) { try { bis.close(); } catch (IOException e) { service.log(e); } } } connectionState = CONNSTATE_FILE_HEADER; service.log(file.getName()+" sent"); } private void cleanup() { try { socket.close(); ftMessages.remove(message); activeTransfers.remove(ProtocolUtils.bytes2LongBE(message.messageId, 0)); if (server != null){ server.close(); server = null; } } catch (IOException e) { } } private synchronized void fileData(byte[] blob, int read, final long bytesLeft) { if (currentFileStream != null) { try { /* * if (connectionState == CONNSTATE_FILE_HEADER){ * currentFileStream.close(); currentFileStream = null; } * else if (connectionState == CONNSTATE_FILE_BODY){ * currentFileStream.write(blob); } */ if (connectionState == CONNSTATE_FILE_BODY) { currentFileStream.write(blob, 0, read); currentFileStream.flush(); if (bytesLeft < 1) { connectionState = CONNSTATE_FILE_HEADER; final String filename = currentFileStream.file.getAbsolutePath(); currentFileStream.close(); currentFileStream = null; service.log(currentFileName + " got"); sendNotification( message.messageId, filename, currentFileSize, currentFileSize - bytesLeft, true, null, participantUid); } else { sendNotification( message.messageId, currentFileStream.getFile().getAbsolutePath(), currentFileSize, currentFileSize - bytesLeft, true, null, participantUid); } } // messageId, filename, totalSize, sizeTransferred, // isReceive, error } catch (IOException e) { Logger.log(e); try { currentFileStream.close(); } catch (IOException e1) { Logger.log(e); } currentFileStream = null; } } } public void fireTransfer() { service.log("client ready, proceed FT"); if (message.receiverId.equals(service.getUn())){ message.receiverId = message.senderId; message.senderId = service.getUn(); } if (files != null){ if (currentFileSizeLeft < 1){ sendFileInfo(files.get(0)); } } } } private synchronized void sendNotification(byte[] messageId, String filename, long totalSize, long sizeSent, boolean incoming, String error, String participantUid){ NotificationData data = new NotificationData(messageId, filename, totalSize, sizeSent, incoming, error, participantUid); notifications.add(data); new Thread("Notification"){ @Override public void run(){ sendNotifications(); } }.start(); } public long getChecksum(File file) throws IOException, IllegalStateException { long sum = 0; long end = file.length(); RandomAccessFile aFile = null; try { FileTransferChecksum summer = new FileTransferChecksum(); ByteBuffer buffer = ByteBuffer.allocate(1024); long remaining = end; aFile = new RandomAccessFile(file, "r"); FileChannel channel = aFile.getChannel(); while (remaining > 0) { buffer.rewind(); buffer.limit((int) Math.min(remaining, buffer.capacity())); int count = channel.read(buffer); if (count == -1) break; buffer.flip(); remaining -= buffer.limit(); summer.update(buffer.array(), buffer.arrayOffset(), buffer.limit()); } if (remaining > 0) { throw new IOException("could not get checksum for entire file; " + remaining + " failed of " + end); } sum = summer.getValue(); } finally { if (aFile != null) { aFile.close(); } } return sum; } private void sendNotifications() { synchronized (notifications) { while (notifications.size() > 0){ NotificationData data = notifications.remove(0); service.getServiceResponse().respond(ICQServiceResponse.RES_FILEPROGRESS, data.messageId, data.filePath, data.totalSize, data.sent, data.incoming, data.error, data.participantUid); } } } private byte[] getHandshakeData(ICBMMessage message) { service.log("get handshake for "+message.externalPort+" id "+ProtocolUtils.getHexString(message.messageId)); byte[] header = new byte[12]; Arrays.fill(header, (byte) 0); header[2] = 0x04; header[3] = 0x4a; if (message.connectFTProxy){ header[5] = 0x4; } else { header[5] = 0x2; } byte[] uinBytes; try { uinBytes = message.senderId.getBytes("ASCII"); } catch (UnsupportedEncodingException e1) { uinBytes = message.senderId.getBytes(); } TLV clsidTlv = new TLV(); clsidTlv.type = 1; clsidTlv.value = ICQConstants.CLSID_AIM_FILESEND; byte[] tlvBytes = service.getDataParser().tlvs2Bytes(new TLV[] { clsidTlv }); byte[] out = new byte[21 + uinBytes.length + tlvBytes.length + ((message.connectFTProxy) ? 2 : 0)]; byte pos = 0; System.arraycopy(header, 0, out, pos, header.length); pos += header.length; out[pos] = (byte) uinBytes.length; pos++; System.arraycopy(uinBytes, 0, out, pos, uinBytes.length); pos += uinBytes.length; if (message.connectFTProxy){ System.arraycopy(ProtocolUtils.short2ByteBE((short) message.externalPort), 0, out, pos, 2); pos+=2; } System.arraycopy(message.messageId, 0, out, pos, 8); pos += 8; System.arraycopy(tlvBytes, 0, out, pos, tlvBytes.length); System.arraycopy(ProtocolUtils.short2ByteBE((short) (out.length - 2)), 0, out, 0, 2); return out; } public void transferFailed(Exception e, String filename, ICBMMessage message, String participantUid) { if (!message.senderId.equals(service.getUn())){ message.receiverId = participantUid; message.senderId = service.getUn(); } message.rvMessageType = 1; service.getMessagingEngine().sendFileMessageReject(message); sendNotification(message.messageId, filename, 100, 0, false, e.getLocalizedMessage(), participantUid); } private ExtendedBufferedOutputStream createFile(String filename, long filesize, long modTime, ICBMMessage message, String participantUid) { // Dummy String storageState = Environment.getExternalStorageState(); if (storageState.equals(Environment.MEDIA_MOUNTED)) { try { File file = (File) service.getServiceResponse().respond(ICQServiceResponse.RES_GET_FILE_FOR_SAVING, filename, service.getBuddyList().findBuddyByUin(participantUid), modTime); FileOutputStream fos = new FileOutputStream(file, true); ExtendedBufferedOutputStream os = new ExtendedBufferedOutputStream(file, fos); return os; } catch (IOException e) { e.printStackTrace(); } } else { transferFailed(new IOException("No storage mounted"), filename, message, participantUid); } return null; } private byte[] getFileInfoByteBlock(byte[] blob, short state) { byte[] out = new byte[blob.length + 6]; out[0] = 0x4f; // file header out[1] = 0x46; out[2] = 0x54; out[3] = 0x32; System.arraycopy(ProtocolUtils.short2ByteBE((short) (blob.length + 6)), 0, out, 4, 2); System.arraycopy(ProtocolUtils.short2ByteBE(state), 0, blob, 0, 2); System.arraycopy(blob, 0, out, 6, blob.length); return out; } private Flap getAcceptMessage(ICBMMessage message){ message.rvMessageType = 2; Flap flap = new Flap(); flap.channel = ICQConstants.FLAP_CHANNELL_DATA; Snac data = new Snac(); data.serviceId = ICQConstants.SNAC_FAMILY_MESSAGING; data.subtypeId = ICQConstants.SNAC_MESSAGING_SENDTHROUGHSERVER; data.requestId = ICQConstants.SNAC_MESSAGING_SENDTHROUGHSERVER; TLV tlv2711 = new TLV(); tlv2711.type = 0x2711; byte[] tlv5data = service.getDataParser().tlvs2Bytes(new TLV[]{tlv2711}); byte[] tlv5fullData = new byte[26 + tlv5data.length]; System.arraycopy(ProtocolUtils.short2ByteBE(message.rvMessageType), 0, tlv5fullData, 0, 2); System.arraycopy(message.messageId, 0, tlv5fullData, 2, 8); System.arraycopy(ICQConstants.CLSID_AIM_FILESEND, 0, tlv5fullData, 10, 16); System.arraycopy(tlv5data, 0, tlv5fullData, 26, tlv5data.length); TLV ch2messageTLV = new TLV(); ch2messageTLV.type = 0x5; ch2messageTLV.value = tlv5fullData; byte[] uidBytes; try { uidBytes = message.receiverId.getBytes("ASCII"); } catch (UnsupportedEncodingException e) { uidBytes = message.receiverId.getBytes(); } byte[] snacRawData = new byte[11 + uidBytes.length]; System.arraycopy(message.messageId, 0, snacRawData, 0, 8); System.arraycopy(ProtocolUtils.short2ByteBE((short) 2), 0, snacRawData, 8, 2); snacRawData[10] = (byte) message.receiverId.length(); System.arraycopy(uidBytes, 0, snacRawData, 11, uidBytes.length); data.data = new TLV[] { ch2messageTLV }; data.plainData = snacRawData; flap.data = data; return flap; } private Flap getRedirectToProxyMessage(String rvIp, int portId, ICBMMessage message, int seqNum) { Flap flap = new Flap(); flap.channel = ICQConstants.FLAP_CHANNELL_DATA; Snac data = new Snac(); data.serviceId = ICQConstants.SNAC_FAMILY_MESSAGING; data.subtypeId = ICQConstants.SNAC_MESSAGING_SENDTHROUGHSERVER; data.requestId = ICQConstants.SNAC_MESSAGING_SENDTHROUGHSERVER; /* * TLV internalIpTLV = new TLV(); internalIpTLV.setType(0x03); byte[] * buffer; try { buffer = InetAddress.getLocalHost().getAddress(); } * catch (UnknownHostException e) { buffer = new byte[]{0,0,0,0}; } * internalIpTLV.setValue(buffer); * * TLV portTLV = new TLV(); portTLV.setType(0x05); * portTLV.setValue(Utils.short2ByteBE(ICQConstants.ICBM_PORT)); */ TLV unknownA = new TLV(); unknownA.type = 0xa; unknownA.value = new byte[] { 0, (byte) seqNum }; TLV rvIPtlv = new TLV(); rvIPtlv.type = 2; rvIPtlv.value = ProtocolUtils.ipString2ByteBE(rvIp); TLV xoredRvIpTlv = new TLV(); xoredRvIpTlv.type = 0x16; xoredRvIpTlv.value = ProtocolUtils.unxorByteArray(rvIPtlv.value); TLV extPortTlv = new TLV(); extPortTlv.type = 5; extPortTlv.value = ProtocolUtils.short2ByteBE((short) portId); TLV xoredPortTlv = new TLV(); xoredPortTlv.type = 0x17; xoredPortTlv.value = ProtocolUtils.unxorByteArray(extPortTlv.value); TLV redirectTlv = new TLV(); redirectTlv.type = 0x10; TLV[] tlv5content = new TLV[] { unknownA, rvIPtlv, xoredRvIpTlv, extPortTlv, xoredPortTlv, redirectTlv }; byte[] tlv5data = service.getDataParser().tlvs2Bytes(tlv5content); byte[] tlv5fullData = new byte[26 + tlv5data.length]; System.arraycopy(ProtocolUtils.short2ByteBE(message.rvMessageType), 0, tlv5fullData, 0, 2); System.arraycopy(message.messageId, 0, tlv5fullData, 2, 8); System.arraycopy(ICQConstants.CLSID_AIM_FILESEND, 0, tlv5fullData, 10, 16); System.arraycopy(tlv5data, 0, tlv5fullData, 26, tlv5data.length); TLV ch2messageTLV = new TLV(); ch2messageTLV.type = 0x5; ch2messageTLV.value = tlv5fullData; byte[] uidBytes; try { uidBytes = message.receiverId.getBytes("ASCII"); } catch (UnsupportedEncodingException e) { uidBytes = message.receiverId.getBytes(); } byte[] snacRawData = new byte[11 + uidBytes.length]; System.arraycopy(message.messageId, 0, snacRawData, 0, 8); System.arraycopy(ProtocolUtils.short2ByteBE((short) 2), 0, snacRawData, 8, 2); snacRawData[10] = (byte) message.receiverId.length(); System.arraycopy(uidBytes, 0, snacRawData, 11, uidBytes.length); data.data = new TLV[] { ch2messageTLV }; data.plainData = snacRawData; flap.data = data; return flap; } public long sendFiles(final ICBMMessage message) { message.rvMessageType = 0; if (message.messageId == null) { message.messageId = new byte[8]; ICBMMessagingEngine.RANDOM.nextBytes(message.messageId); } message.externalPort = service.getLoginPort(); message.rvIp = ProtocolUtils.getIPString(service.getInternalIp()); ftMessages.add(message); new Thread("File sender "+message.receiverId){ @Override public void run(){ try { List<File> files = message.getFileList(); createPeer(message, files); } catch (IOException e) { Logger.log(e); connectProxy(message, activeTransfers.get(ProtocolUtils.bytes2LongBE(message.messageId, 0))); } } }.start(); return ProtocolUtils.bytes2LongBE(message.messageId); } private void sendFileTransferRequest(ICBMMessage message, List<File> files){ short type = 1; TLV tlv2711 = null; TLV unknownA = new TLV(); unknownA.type = 0xa; if (files != null){ int count = files.size(); long filesize = 0; for (File f : files) { filesize += f.length(); } byte[] filename; if (files.size() == 1) { try { filename = files.get(0).getName().getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { filename = files.get(0).getName().getBytes(); } } else { filename = new byte[0]; } byte[] tlv2711data = new byte[9 + filename.length]; int pos = 0; System.arraycopy(ProtocolUtils.short2ByteBE(type), 0, tlv2711data, pos, 2); pos += 2; System.arraycopy(ProtocolUtils.short2ByteBE((short) count), 0, tlv2711data, pos, 2); pos += 2; System.arraycopy(ProtocolUtils.int2ByteBE((int) filesize), 0, tlv2711data, pos, 4); pos += 4; System.arraycopy(filename, 0, tlv2711data, pos, filename.length); tlv2711 = new TLV(); tlv2711.type = 0x2711; tlv2711.value = tlv2711data; unknownA.value = new byte[] { 0, 1 }; } else { unknownA.value = new byte[] { 0, 2 }; } Flap flap = new Flap(); flap.channel = ICQConstants.FLAP_CHANNELL_DATA; Snac data = new Snac(); data.serviceId = ICQConstants.SNAC_FAMILY_MESSAGING; data.subtypeId = ICQConstants.SNAC_MESSAGING_SENDTHROUGHSERVER; data.requestId = ICQConstants.SNAC_MESSAGING_SENDTHROUGHSERVER; /* * TLV internalIpTLV = new TLV(); internalIpTLV.setType(0x03); byte[] * buffer; try { buffer = InetAddress.getLocalHost().getAddress(); } * catch (UnknownHostException e) { buffer = new byte[]{0,0,0,0}; } * internalIpTLV.setValue(buffer); * * TLV portTLV = new TLV(); portTLV.setType(0x05); * portTLV.setValue(Utils.short2ByteBE(ICQConstants.ICBM_PORT)); */ TLV unknownF = new TLV(); unknownF.type = 0xf; TLV rvIPtlv = new TLV(); rvIPtlv.type = 2; rvIPtlv.value = ProtocolUtils.ipString2ByteBE(message.rvIp); TLV xoredRvIpTlv = new TLV(); xoredRvIpTlv.type = 0x16; xoredRvIpTlv.value = ProtocolUtils.unxorByteArray(rvIPtlv.value); TLV extPortTlv = new TLV(); extPortTlv.type = 5; extPortTlv.value = ProtocolUtils.short2ByteBE((short) message.externalPort); TLV xoredPortTlv = new TLV(); xoredPortTlv.type = 0x17; xoredPortTlv.value = ProtocolUtils.unxorByteArray(extPortTlv.value); TLV internalIPTlv = new TLV(); internalIPTlv.type = 0x3; internalIPTlv.value = ProtocolUtils.ipString2ByteBE(message.rvIp); TLV tlv2712 = new TLV(); tlv2712.type = 0x2712; tlv2712.value = new String("utf-8").getBytes(); /* * TLV tlv2713 = new TLV(); tlv2713.type = 0x2713; tlv2713.value = new * byte[8]; Arrays.fill(tlv2713.value, (byte) 0); */ TLV[] tlv5content; if (files != null){ tlv5content = new TLV[] { unknownA, unknownF, rvIPtlv, xoredRvIpTlv, internalIPTlv, extPortTlv, xoredPortTlv, tlv2711, tlv2712 }; } else { tlv5content = new TLV[] { unknownA, unknownF, rvIPtlv, xoredRvIpTlv, internalIPTlv, extPortTlv, xoredPortTlv }; } byte[] tlv5data = service.getDataParser().tlvs2Bytes(tlv5content); byte[] tlv5fullData = new byte[26 + tlv5data.length]; System.arraycopy(ProtocolUtils.short2ByteBE(message.rvMessageType), 0, tlv5fullData, 0, 2); System.arraycopy(message.messageId, 0, tlv5fullData, 2, 8); System.arraycopy(ICQConstants.CLSID_AIM_FILESEND, 0, tlv5fullData, 10, 16); System.arraycopy(tlv5data, 0, tlv5fullData, 26, tlv5data.length); TLV ch2messageTLV = new TLV(); ch2messageTLV.type = 0x5; ch2messageTLV.value = tlv5fullData; byte[] uidBytes; try { uidBytes = message.receiverId.getBytes("ASCII"); } catch (UnsupportedEncodingException e) { uidBytes = message.receiverId.getBytes(); } byte[] snacRawData = new byte[11 + uidBytes.length]; System.arraycopy(message.messageId, 0, snacRawData, 0, 8); System.arraycopy(ProtocolUtils.short2ByteBE((short) 2), 0, snacRawData, 8, 2); snacRawData[10] = (byte) message.receiverId.length(); System.arraycopy(uidBytes, 0, snacRawData, 11, uidBytes.length); data.data = new TLV[] { ch2messageTLV }; data.plainData = snacRawData; flap.data = data; service.getRunnableService().sendToSocket(flap); } private ServerSocket createLocalSocket(final FileRunnableService frs) throws IOException { final ServerSocket server = new ServerSocket(0); new Thread("FT Server socket listener") { @Override public void run() { try { server.setSoTimeout(SERVER_SOCKET_TIMEOUT); Socket socket = server.accept(); frs.socket = socket; service.log("client connected"); frs.start(); } catch (Exception e) { service.log(e); } } }.start(); return server; } public void redirectRequest(ICBMMessage message) { FileRunnableService runnable = activeTransfers.get(ProtocolUtils.bytes2LongBE(message.messageId, 0)); if (runnable == null) { return; } runnable.message = message; if (message.connectFTProxy){ connectProxy(message, runnable); } else if (message.connectFTPeer){ connectPeer(message, runnable, false); } } public void fireTransfer(ICBMMessage message) { FileRunnableService runnable = activeTransfers.get(ProtocolUtils.bytes2LongBE(message.messageId, 0)); if (runnable == null) { return; } runnable.fireTransfer(); } private class ExtendedBufferedOutputStream extends BufferedOutputStream { private final File file; public ExtendedBufferedOutputStream(File file, OutputStream os) { super(os, 88000); this.file = file; } public File getFile() { return file; } } public void cancel(Long messageId) { if (findMessageByMessageId(messageId) == null) { return; }; fileReceiveResponse(messageId, false); FileRunnableService runnable = activeTransfers.get(messageId); if (runnable == null) { return; } if (runnable.socket!=null && !runnable.socket.isClosed()){ try { runnable.socket.close(); } catch (IOException e) { service.log(e); } } activeTransfers.remove(messageId); removeMessageByMessageId(messageId); } private class NotificationData { public byte[] messageId; public String filePath; public long totalSize; public long sent; public boolean incoming; public String error; public String participantUid; public NotificationData( byte[] messageId, String filePath, long totalSize, long sent, boolean incoming, String error, String participantUid){ this.messageId = messageId; this.filePath = filePath; this.totalSize = totalSize; this.sent = sent; this.incoming = incoming; this.error = error; this.participantUid = participantUid; } } public void cancelAll() { for (FileRunnableService runnable: activeTransfers.values()){ if (runnable.socket != null && !runnable.socket.isClosed()){ try { runnable.socket.close(); } catch (IOException e) { service.log(e); } } } } /** * An implementation of the checksumming method used by AOL Instant Messenger's * file transfer protocol. */ public final class FileTransferChecksum { /** The checksum of an empty set of data. */ public static final long CHECKSUM_EMPTY = 0xffff0000L; /** The checksum value. */ private long checksum; { // init reset(); } /** * Creates a new file transfer checksum computer object. */ public FileTransferChecksum() { } public void update(int value) { update(new byte[] { (byte) value }, 0, 1); } public void update(final byte[] input, final int offset, final int len) { if (input == null){ return; } assert checksum >= 0; long check = (checksum >> 16) & 0xffffL; for (int i = 0; i < len; i++) { final long oldcheck = check; final int byteVal = input[offset + i] & 0xff; final int val; if ((i & 1) != 0) val = byteVal; else val = byteVal << 8; check -= val; if (check > oldcheck) check--; } check = ((check & 0x0000ffff) + (check >> 16)); check = ((check & 0x0000ffff) + (check >> 16)); checksum = check << 16 & 0xffffffffL; assert checksum >= 0; } public long getValue() { assert checksum >= 0; return checksum; } public void reset() { checksum = CHECKSUM_EMPTY; assert checksum >= 0; } public String toString() { return "FileTransferChecksum: " + checksum; } } }